/*
 * acooly.cn Inc.
 * Copyright (c) 2016 All Rights Reserved.
 * create by zhike
 * date:2016年4月4日
 *
 */
package com.acooly.module.openapi.client.provider.sinapay.marshall;

import com.acooly.core.utils.Collections3;
import com.acooly.core.utils.Encodes;
import com.acooly.core.utils.Reflections;
import com.acooly.core.utils.Strings;
import com.acooly.core.utils.conversion.TypeConverterUtils;
import com.acooly.core.utils.enums.Messageable;
import com.acooly.core.utils.mapper.JsonMapper;
import com.acooly.core.utils.validate.Validators;
import com.acooly.module.openapi.client.api.anotation.ApiItem;
import com.acooly.module.openapi.client.api.exception.ApiClientException;
import com.acooly.module.openapi.client.api.message.ApiMessage;
import com.acooly.module.openapi.client.api.message.MessageFactory;
import com.acooly.module.openapi.client.api.secrity.RSACryptoHandler;
import com.acooly.module.openapi.client.api.util.ApiClientUtils;
import com.acooly.module.openapi.client.api.util.ApiTypes;
import com.acooly.module.openapi.client.provider.sinapay.OpenAPIClientSinapayProperties;
import com.acooly.module.openapi.client.provider.sinapay.SinapayConstants;
import com.acooly.module.openapi.client.provider.sinapay.annotation.ApiDto;
import com.acooly.module.openapi.client.provider.sinapay.annotation.ApiTransient;
import com.acooly.module.openapi.client.provider.sinapay.annotation.ItemOrder;
import com.acooly.module.openapi.client.provider.sinapay.domain.SinapayApiMsg;
import com.acooly.module.openapi.client.provider.sinapay.domain.SinapayMessage;
import com.acooly.module.openapi.client.provider.sinapay.domain.SinapayRequest;
import com.acooly.module.openapi.client.provider.sinapay.domain.SinapayResponse;
import com.acooly.module.openapi.client.provider.sinapay.enums.SinapayApiServiceType;
import com.acooly.module.openapi.client.provider.sinapay.exception.SinapayProcessingException;
import com.acooly.module.openapi.client.provider.sinapay.message.Dtoable;
import com.acooly.module.openapi.client.provider.sinapay.utils.MoneyTypeConverter;
import com.acooly.module.safety.Safes;
import com.acooly.module.safety.key.KeyLoadManager;
import com.acooly.module.safety.signature.SignTypeEnum;
import com.acooly.module.safety.support.KeyPair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author zhike
 */
@Slf4j
public class SinapayAbstractMarshall {

//    protected CryptoHandler cryptoHander = new RSACryptoHandler(getKeyPair());

    @Autowired
    protected OpenAPIClientSinapayProperties openAPIClientFudianProperties;

    @Autowired
    protected KeyLoadManager keyStoreLoadManager;

    @Resource(name = "sinapayMessageFactory")
    protected MessageFactory messageFactory;

    static {
        TypeConverterUtils.getTypeConverterManager().register(new MoneyTypeConverter());
    }

    protected String doMarshall(SinapayRequest source) {
        try {
            source.setPartner(getProperties().getPartnerId());
            settingNotifyUrl(source);
            doVerifyParam(source);
            encrypt(source);
            String waitSignText = getMessage(convertMarshall(source, true), false);
            String signature = doSign(waitSignText);
            source.setSign(signature);
            String message = getMessage(convertMarshall(source, false), true);
            log.info("请求报文: {}", message);
            return message;
        }catch (Exception e) {
            log.info("组装请求报文异常：{}",e.getMessage());
            throw  new ApiClientException("组装请求报文异常："+e.getMessage());
        }
    }


    protected SinapayResponse doUnMarshall(String message, String serviceName, boolean notify) {
        try {
            String responseMessage = Encodes.urlDecode(message);
            log.info("响应报文:{}", responseMessage);
            Map<String, String> responseMap = JsonMapper.nonDefaultMapper().fromJson(responseMessage, Map.class);
            String sign = responseMap.get("sign");
            String responseCode = responseMap.get("response_code");
            SinapayResponse sinapayResponse = null;
            if (notify) {
                sinapayResponse = (SinapayResponse) messageFactory.getNotify(serviceName);
            } else {
                sinapayResponse = (SinapayResponse) messageFactory.getResponse(serviceName);
            }
            convertUnmarshall(responseMap, sinapayResponse);
            if (Strings.equals(SinapayConstants.REQUEST_SUCCESS, responseCode)) {
                responseMap.remove("sign");
                responseMap.remove("sign_type");
                responseMap.remove("sign_version");
                String signMessage = getMessage(responseMap, false);
                doVerify(signMessage, sign);
            }
            return sinapayResponse;
        } catch (Exception e) {
            throw new SinapayProcessingException("解析报文错误:" + e.getMessage());
        }
    }

    public void settingNotifyUrl(SinapayRequest apiMessage) {
        SinapayApiMsg sinapayApiMsg = SinapayConstants.getApiMsgInfo(apiMessage);
        apiMessage.setService(sinapayApiMsg.service().getCode());
        if (Strings.isBlank(apiMessage.getNotifyUrl()) && sinapayApiMsg.service().getApiServiceType() != SinapayApiServiceType.SYNC) {
            apiMessage.setNotifyUrl(SinapayConstants.getCanonicalUrl(openAPIClientFudianProperties.getDomain(),
                    openAPIClientFudianProperties.getNotifyUrl()
                            + sinapayApiMsg.service().code()));
        }
        if (sinapayApiMsg.service().getApiServiceType() == SinapayApiServiceType.REDIRECT && Strings.isBlank(apiMessage.getReturnUrl())) {
            throw new RuntimeException("跳转接口returnUrl是必须的");
        }
    }

    protected OpenAPIClientSinapayProperties getProperties() {
        return openAPIClientFudianProperties;
    }


    protected void doVerifyParam(SinapayMessage source) {
        try {
            source.doCheck();
            Validators.assertJSR303(source);
        } catch (Exception e) {
            throw new ApiClientException(e.getMessage());
        }
    }

    protected String doSign(String waitForSign) {
        return Safes.getSigner(SignTypeEnum.Rsa).sign(waitForSign, getKeyPair());
    }

    protected void doVerify(String plain, String sign) {
        try {
            Safes.getSigner(SignTypeEnum.Rsa).verify(plain, getKeyPair(), sign);
            log.info("验签成功");
        }catch (Exception e) {
            throw new ApiClientException("验签失败："+e.getMessage());
        }
    }

    protected KeyPair getKeyPair() {
        return keyStoreLoadManager.load(SinapayConstants.PROVIDER_DEF_PRINCIPAL, SinapayConstants.SIGN_PROVIDER_NAME);
    }

    protected KeyPair getEncriyptKeyPair() {
        return keyStoreLoadManager.load(SinapayConstants.PROVIDER_DEF_PRINCIPAL, SinapayConstants.ENCRYPT_PROVIDER_NAME);
    }

    protected String getMessage(Map<String, String> map, boolean encode) {
        String message = null;
        Map<String, String> sortedMap = new TreeMap<>(map);
        StringBuilder messageBuilder = new StringBuilder();
        if (sortedMap.size() > 0) {
            for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
                // 注意：新浪变态的对值做两次urlEncode
                if (entry.getValue() != null) {
                    messageBuilder.append(entry.getKey()).append("=")
                            .append(encode ? Encodes.urlEncode(Encodes.urlEncode(entry.getValue())) : entry.getValue())
                            .append("&");
                }
            }
            messageBuilder.deleteCharAt(messageBuilder.length() - 1);
            message = messageBuilder.toString();
        }
        return message;
    }

    protected Map<String, String> convertMarshall(ApiMessage apiMessage, boolean filterNoSign) {
        Map<String, String> map = Maps.newHashMap();
        Set<Field> fields = Reflections.getFields(apiMessage.getClass());
        String key = null;
        String value = null;
        for (Field f : fields) {

            ApiTransient apiTransient = f.getAnnotation(ApiTransient.class);
            if (apiTransient != null) {
                continue;
            }
            value = getPropertyValue(Reflections.invokeGetter(apiMessage, f.getName()));
            if (Strings.isBlank(value)) {
                continue;
            }

            ApiItem apiItem = f.getAnnotation(ApiItem.class);
            if (apiItem != null) {
                // 跳过不签名的数据项
                if (filterNoSign && !apiItem.sign()) {
                    continue;
                }
                key = Strings.isBlankDefault(apiItem.value(), f.getName());
            } else {
                key = f.getName();
            }
            map.put(key, value);
        }
        return map;
    }

    protected void encrypt(ApiMessage apiMessage) {
        Set<Field> fields = Reflections.getFields(apiMessage.getClass());
        for (Field f : fields) {
            ApiItem apiItem = f.getAnnotation(ApiItem.class);
            if (apiItem != null && apiItem.securet()) {
                Reflections.invokeSetter(apiMessage, f.getName(),
                        doEncrypt(getPropertyValue(Reflections.invokeGetter(apiMessage, f.getName()))));
            }
        }
    }

    private String unmarshallDto(Object object, char col, char row) {
        Map<Integer, String> data = Maps.newTreeMap();
        Set<Field> fields = Reflections.getFields(object.getClass());
        ItemOrder itemOrder = null;
        for (Field f : fields) {
            itemOrder = f.getAnnotation(ItemOrder.class);
            if (itemOrder != null) {
                data.put(itemOrder.value(), getPropertyValue(Reflections.invokeGetter(object, f.getName())));
            }
        }
        StringBuilder sb = new StringBuilder();
        for (String v : data.values()) {
            sb.append(v).append(col);
        }
        return sb.substring(0, sb.length() - 1) + row;
    }

    private String getPropertyValue(Object object) {
        if (object == null) {
            return "";
        }
        if (ApiTypes.isCollection(object.getClass())) {
            if (Collections3.isEmpty((Collection<?>) object)) {
                return "";
            }
            Collection<?> c = (Collection<?>) object;
            ApiDto anno = Collections3.getFirst(c).getClass().getAnnotation(ApiDto.class);
            char colSep = '^';
            char rowSep = '|';
            if (anno != null) {
                colSep = anno.colSeparator();
                rowSep = anno.rowSeparator();
            }
            StringBuilder sb = new StringBuilder();
            for (Object obj : c) {
                sb.append(unmarshallDto(obj, colSep, rowSep));
            }
            return sb.substring(0, sb.length() - 1);
        } else if (Dtoable.class.isAssignableFrom(object.getClass())) {
            ApiDto anno = object.getClass().getAnnotation(ApiDto.class);
            char colSep = '^';
            char rowSep = '|';
            if (anno != null) {
                colSep = anno.colSeparator();
                rowSep = anno.rowSeparator();
            }
            return unmarshallDto(object, colSep, rowSep);
        } else if (Messageable.class.isAssignableFrom(object.getClass())) {
            return ((Messageable) object).code();
        } else if (object.getClass().isEnum()) {
            return ((Enum<?>) object).name();
        } else {
            return TypeConverterUtils.convert(object, String.class);
        }
    }

    protected String doEncrypt(String value) {
        RSACryptoHandler cryptoHandler = new RSACryptoHandler(getEncriyptKeyPair());
        return cryptoHandler.encrypt(value);
    }

    protected <T extends ApiMessage> T convertUnmarshall(Map<String, String> responseMap, T response) {
        Set<Field> fields = Reflections.getFields(response.getClass());
        String key = null;
        String value = null;
        for (Field f : fields) {
            ApiItem apiItem = f.getAnnotation(ApiItem.class);
            if (apiItem != null) {
                key = Strings.isBlankDefault(apiItem.value(), f.getName());
            } else {
                key = f.getName();
            }
            value = responseMap.get(key);
            if (Strings.isBlank(value)) {
                continue;
            }
            Object result = null;
            // 根据sina的报文特色，以下处理约定：在报文中定义集合类只有List（支持List泛型为自定义bean和String，但不支持泛型为集合和MAP对象），叶子属性的类型全部为String。
            if (ApiTypes.isCollection(f)) {
                // 集合类型（泛型）
                Class<?> genericClass = ApiClientUtils.getFieldGenericType(f);
                ApiDto anno = genericClass.getAnnotation(ApiDto.class);
                char colSep = '^';
                char rowSep = '|';
                if (anno != null) {
                    colSep = anno.colSeparator();
                    rowSep = anno.rowSeparator();
                }
                List<String> rows = Lists.newArrayList(Strings.split(value, rowSep));
                if (!Collections3.isEmpty(rows)) {
                    if (ApiTypes.isSimpleType(genericClass)) {
                        result = rows;
                    } else {
                        // dto对象必须定义@ApiOrder顺序
                        result = marshallDtos(genericClass, rows, colSep);
                    }
                }
            } else if (Dtoable.class.isAssignableFrom(f.getType())) {
                // DTO对象
                ApiDto anno = f.getType().getAnnotation(ApiDto.class);
                char colSep = '^';
                if (anno != null) {
                    colSep = anno.colSeparator();
                }
                result = marshallDto(f.getType(), value, colSep);
            } else {
                // 其他类型
                result = TypeConverterUtils.convertValue(value, f.getType());
            }
            Reflections.invokeSetter(response, f.getName(), result);
        }
        return response;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    protected List marshallDtos(Class<?> clazz, List<String> rows, char colSep) {
        List list = Lists.newArrayList();
        Object object = null;

        for (String row : rows) {
            object = marshallDto(clazz, row, colSep);
            if (object != null) {
                list.add(object);
            }
        }
        return list;
    }

    private Object marshallDto(Class<?> clazz, String data, char colSep) {
        Object object = null;
        ItemOrder itemOrder = null;
        String[] items = Strings.splitPreserveAllTokens(data, colSep);
        if (items == null || items.length == 0) {
            return null;
        }
        try {
            object = clazz.newInstance();
        } catch (Exception e) {
        }
        Set<Field> fields = Reflections.getFields(clazz);
        for (Field f : fields) {
            itemOrder = f.getAnnotation(ItemOrder.class);
            if (itemOrder != null && itemOrder.value() < items.length && Strings.isNotBlank(items[itemOrder.value()])) {
                Reflections.invokeSetter(object, f.getName(),
                        TypeConverterUtils.convertValue(items[itemOrder.value()], f.getType()));
            }
        }
        return object;
    }

}
